﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.DependencyInjection.Impl;
using StyleMVVM.Logging;
using StyleMVVM.Messenger;
using StyleMVVM.Utilities;
using StyleMVVM.Validation;

namespace StyleMVVM.Data.Activation
{
	/// <summary>
	/// The CompiledActivationStrategy is the heart of the DependencyInjectionContainer
	/// Using Linq Expressions it compiles a method that will construct an object
	/// using the provided parameters. 
	/// Then import all needed properties and Register for all messages.
	/// </summary>
	public sealed class CompiledActivationStrategy : IActivationStrategy
	{
		private static readonly string supplemental = typeof(CompiledActivationStrategy).FullName;
		private static readonly PropertyInfo objectArrayIndexInfo = null;
		private static readonly PropertyInfo objectArrayLengthInfo = null;
		private static readonly MethodInfo messengerRegisterInfo = null;
		private static readonly ConstructorInfo exceptionConstructorInfo = null;
		private static readonly MethodInfo createUsingExportFunctionInfo = null;
		private static readonly MethodInfo genericActivateInfo = null;
		private static readonly MethodInfo internalLocateInfo = null;
		private static readonly MethodInfo lazyLocateInfo = null;
		private static readonly MethodInfo exportStrategyActivateMethodInfo = null;
		private static readonly MethodInfo createFuncFactoryMethodInfo = null;
		private static readonly MethodInfo createFuncFactoryWithContextMethodInfo = null;
		private static readonly MethodInfo createFuncFactoryWithContextAndParamsMethodInfo = null;
		private static readonly MethodInfo customInitializationMethodInfo = null;
		private static readonly MethodInfo enrichWithMethodInfo = null;

		static CompiledActivationStrategy()
		{
			objectArrayIndexInfo = typeof(IList).GetTypeInfo().GetDeclaredProperty("Item");

			objectArrayLengthInfo = typeof(ICollection).GetTypeInfo().GetDeclaredProperty("Count");

			messengerRegisterInfo = typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("RegisterMessengerHandler");

			createUsingExportFunctionInfo =
				typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("CreateUsingExportFunction");

			foreach (ConstructorInfo declaredConstructor in typeof(Exception).GetTypeInfo().DeclaredConstructors)
			{
				if (declaredConstructor.GetParameters().Count() == 1 &&
					 declaredConstructor.GetParameters().FirstOrDefault().ParameterType == typeof(string))
				{
					exceptionConstructorInfo = declaredConstructor;
				}
			}

			genericActivateInfo = typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("GenericActivate");

			internalLocateInfo = typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("Locate");

			lazyLocateInfo = typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("LazyLocate");

			createFuncFactoryMethodInfo = typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("CreateFuncFactory");

			createFuncFactoryWithContextMethodInfo =
				typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("CreateFuncFactoryWithContext");

			createFuncFactoryWithContextAndParamsMethodInfo =
				typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("CreateFuncFactoryWithContextAndParams");

			exportStrategyActivateMethodInfo = typeof(IExportActivator).GetTypeInfo().GetDeclaredMethod("Activate");

			customInitializationMethodInfo = typeof(ICustomInitialization).GetTypeInfo()
																							  .GetDeclaredMethod("CustomInitialization");

			enrichWithMethodInfo = typeof(CompiledActivationStrategy).GetTypeInfo().GetDeclaredMethod("EnrichWith");
		}

		private ExportFunction exportFunction;
		private ActivationStrategyDelegate activationMethod;
		private IActivationTypeInfo activationTypeInfo;
		private ITypeAugmenter typeAugmenter;
		private IDispatchedMessenger messenger = null;
		private List<ParameterExpression> importedVariables;

		public CompiledActivationStrategy(IActivationTypeInfo activationTypeInfo, ITypeAugmenter typeAugmenter)
		{
			ActivationType = activationTypeInfo.ActivationType;
			exportFunction = activationTypeInfo.ExportFunction;
			this.activationTypeInfo = activationTypeInfo;

			this.typeAugmenter = typeAugmenter;

			importedVariables = new List<ParameterExpression>();
		}

		public IDependencyInjectionContainer Container { get; set; }

		public IActivationService ActivationService { get; set; }

		public bool Suspendable { get; set; }

		public bool RegisterHandlers { get; set; }

		private Type ActivationType { get; set; }

		public void Initialize()
		{
			if (activationMethod != null)
			{
				return;
			}

			if (typeAugmenter != null)
			{
				ActivationType = typeAugmenter.Augment(ActivationType);
			}

			if (activationTypeInfo.DiscoverAttributes)
			{
				DiscoverAttributes();
			}

			if (ActivationType.GetTypeInfo().ImplementedInterfaces.Contains(
				typeof(IDependencyInjectionContainerAware)))
			{
				bool containerFound = false;

				foreach (ImportPropertyInfo importPropertyInfo in activationTypeInfo.ImportProperties)
				{
					if (((PropertyInfo)importPropertyInfo.PropertyInfo).Name == "Container")
					{
						containerFound = true;
						break;
					}
				}
				if (!containerFound)
				{
					ImportPropertyInfo importPropertyInfo =
						new ImportPropertyInfo
							{
								IsRequired = true,
								PropertyInfo = ActivationType.GetTypeInfo().GetDeclaredProperty("Container")
							};

					activationTypeInfo.ImportProperties =
						new List<ImportPropertyInfo>(activationTypeInfo.ImportProperties)
							{
								importPropertyInfo
							};
				}
			}

			try
			{
				activationMethod = GenerateMethod();
			}
			catch (Exception exp)
			{

				throw;
			}

			activationTypeInfo = null;

			importedVariables.Clear();
			importedVariables = null;
		}

		private void DiscoverAttributes()
		{
			List<ImportPropertyInfo> importPropertyInfos = new List<ImportPropertyInfo>();
			List<InitializePropertyInfo> initializePropertyInfos = new List<InitializePropertyInfo>();

			var tempType = ActivationType;

			while (tempType != null)
			{
				foreach (PropertyInfo pi in tempType.GetTypeInfo().DeclaredProperties)
				{
					foreach (Attribute attr in pi.GetCustomAttributes())
					{
						IInitializeAttribute initializeAttribute = attr as IInitializeAttribute;
						IImportAttribute importAttribute = attr as IImportAttribute;

						if (importAttribute != null)
						{
							IImportAttributeInfo attributeInfo = importAttribute.ProvideImportInfo(pi);

							string importName = attributeInfo.ImportName;
							bool isRequired = attributeInfo.Required;
							bool isVolatile = attributeInfo.Volatile;

							ImportPropertyInfo importPropertyInfo =
								new ImportPropertyInfo
									{
										IsVolatile = isVolatile,
										ExportName = importName,
										IsRequired = isRequired,
										PropertyInfo = pi,
										ExportActivator = attributeInfo.ExportActivator
									};

							importPropertyInfos.Add(importPropertyInfo);
						}
						else if (initializeAttribute != null)
						{
							InitializePropertyInfo initializeProperty =
								new InitializePropertyInfo
									{
										PropertyInfo = pi,
										InitializeValue = initializeAttribute.Value
									};

							initializePropertyInfos.Add(initializeProperty);
						}
					}
				}

				tempType = tempType.GetTypeInfo().BaseType;
			}

			activationTypeInfo.ImportProperties = importPropertyInfos;
			activationTypeInfo.InitializeProperties = initializePropertyInfos;
		}

		public IList<object> PipelineHandlers { get; set; }

		public object Activate(IInjectionContext injectionContext, [ReadOnlyArray] object[] constructorParams)
		{
			if (activationMethod != null)
			{
				try
				{
					return activationMethod(injectionContext, constructorParams);
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while activating type: " + ActivationType.FullName,
									 supplemental,
									 exp);
				}
			}

			return null;
		}

		public ActivationStrategyDelegate GetActivationStrategyDelegate()
		{
			return activationMethod;
		}

		internal T GenericActivate<T>(IInjectionContext injectionContext)
		{
			return (T)ActivationService.CreateByType(injectionContext, typeof(T));
		}

		/// <summary>
		/// Generates and returns a Function that creates and satisfies imports
		/// using Linq Expressions to generate a very efficent delegate.
		/// </summary>
		/// <param name="importProperties"></param>
		/// <param name="initializeProperties"> </param>
		/// <returns></returns>
		private ActivationStrategyDelegate GenerateMethod()
		{
			List<Expression> expressions = new List<Expression>();
			List<Expression> importExpressions = new List<Expression>();

			ParameterExpression injectionContextParameter =
				Expression.Parameter(typeof(IInjectionContext), "injectionContext");

			ParameterExpression constructorParams =
				Expression.Parameter(typeof(object[]), "constructorParams");

			ParameterExpression returnValue = Expression.Variable(ActivationType, "returnValue");

			ParameterExpression strategy =
				Expression.Variable(typeof(CompiledActivationStrategy), "strategy");

			ParameterExpression containerParameter =
				Expression.Variable(typeof(IDependencyInjectionContainer), "container");

			// we have an export function so we don't need a constructor
			if (activationTypeInfo.ExportFunction != null)
			{
				expressions.Add(
					generateExportFunctionCall(strategy, containerParameter, injectionContextParameter, constructorParams, returnValue));
			}
			else // we need to generate constructors
			{
				Expression constructorExpression =
					GenerateConstructors(importExpressions, strategy, injectionContextParameter, constructorParams, returnValue);

				expressions.Add(constructorExpression);
			}

			if (typeof(ICustomInitialization).GetTypeInfo().IsAssignableFrom(ActivationType.GetTypeInfo()))
			{
				ProcessCustomInitialization(returnValue, containerParameter, injectionContextParameter, expressions);
			}

			expressions.AddRange(
				ProcessImportProperties(importExpressions, returnValue, strategy, containerParameter, injectionContextParameter));

			ProcessInitializeProperties(returnValue, strategy, injectionContextParameter, expressions);

			MethodInfo activationCompleteCallback;

			expressions.AddRange(
				ProcessMethods(importExpressions, injectionContextParameter, strategy, returnValue, out activationCompleteCallback));

			//
			// TODO: Suspension
			//  

			// insert all import statements to the beginning
			expressions.InsertRange(0, importExpressions);

			// assign strategy variable to this
			expressions.Insert(0, Expression.Assign(strategy, Expression.Constant(this)));

			// assign container value to container variable
			expressions.Insert(0, Expression.Assign(containerParameter, Expression.Constant(Container)));

			BlockExpression customBlock = customActivationLogic(containerParameter, constructorParams, returnValue);

			if (customBlock != null)
			{
				expressions.Add(customBlock);
			}

			if (activationCompleteCallback == null &&
				 ActivationType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IActivationAware)))
			{
				activationCompleteCallback = ActivationType.GetTypeInfo().GetDeclaredMethod("Activate");
			}

			if (activationCompleteCallback != null)
			{
				expressions.Add(Expression.Call(returnValue, activationCompleteCallback));
			}

			if (activationTypeInfo.EnrichWithDelegates != null &&
				 activationTypeInfo.EnrichWithDelegates.Any())
			{
				expressions.Add(
					Expression.Call(strategy,
										 enrichWithMethodInfo,
										 returnValue,
										 injectionContextParameter,
										 Expression.Constant(activationTypeInfo.EnrichWithDelegates)));
			}
			else
			{
				expressions.Add(returnValue);
			}

			List<ParameterExpression> parameterExpressions = new List<ParameterExpression> { containerParameter, strategy, returnValue };
			parameterExpressions.AddRange(importedVariables);

			BlockExpression expression = Expression.Block(parameterExpressions, expressions);

			try
			{
				return Expression.Lambda<ActivationStrategyDelegate>(
					expression, injectionContextParameter, constructorParams).Compile();
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while compiling activation strategy for: " + ActivationType.FullName,
								 supplemental,
								 exp);
				Logger.Error("LambdaExpresion = " + expression, supplemental);
				exp.Data["LambdaExpresion"] = expression.ToString();

				throw;
			}
		}

		private void ProcessCustomInitialization(ParameterExpression returnValue,
			ParameterExpression containerParameter, ParameterExpression injectionContextParameter, List<Expression> expressions)
		{
			expressions.Add(Expression.Call(returnValue, customInitializationMethodInfo, containerParameter, injectionContextParameter));
		}

		private Expression generateExportFunctionCall(ParameterExpression strategy,
																	 ParameterExpression containerParameter,
																	 ParameterExpression injectionContextParameter,
																	 ParameterExpression constructorParams,
																	 ParameterExpression returnValueExpression)
		{
			MethodInfo createMethod = createUsingExportFunctionInfo.MakeGenericMethod(activationTypeInfo.ActivationType);

			return Expression.Assign(returnValueExpression,
											 Expression.Call(strategy,
																  createMethod,
																  containerParameter,
																  injectionContextParameter,
																  constructorParams));
		}


		private void ProcessInitializeProperties(ParameterExpression returnValue,
															  ParameterExpression strategy,
															  ParameterExpression injectionContext,
															  List<Expression> expressions)
		{
			if (activationTypeInfo.InitializeProperties == null)
			{
				return;
			}

			foreach (InitializePropertyInfo initializePropertyInfo in activationTypeInfo.InitializeProperties)
			{
				PropertyInfo propertyInfo = initializePropertyInfo.PropertyInfo as PropertyInfo;

				if (propertyInfo != null)
				{
					if (initializePropertyInfo.InitializeValue != null)
					{
						BinaryExpression assignProperty =
							Expression.Assign(Expression.Property(returnValue, propertyInfo),
													Expression.Convert(Expression.Constant(initializePropertyInfo.InitializeValue),
																			 propertyInfo.PropertyType));

						expressions.Add(assignProperty);
					}
					else if (!propertyInfo.PropertyType.GetTypeInfo().IsPrimitive)
					{
						MethodInfo closedCreate =
							genericActivateInfo.MakeGenericMethod(propertyInfo.PropertyType);

						BinaryExpression assignProperty =
							Expression.Assign(Expression.Property(returnValue, propertyInfo),
													Expression.Convert(Expression.Call(strategy, closedCreate, injectionContext),
																			 propertyInfo.PropertyType));

						expressions.Add(assignProperty);
					}
				}
			}
		}

		private IEnumerable<Expression> ProcessImportProperties(
			List<Expression> importStatements,
			ParameterExpression returnValue,
			ParameterExpression strategy,
			ParameterExpression containerParameter,
			ParameterExpression injectionContextParameter)
		{
			if (activationTypeInfo.ImportProperties == null)
			{
				return new Expression[0];
			}

			List<Expression> returnExpressions = new List<Expression>();

			// Go through all properties that are [Import] and create the statment
			// returnValue.PropertyName = container.Locate("ImportName",null);
			// or returnValue.PropertyName = container.Locate<T>(null);
			foreach (ImportPropertyInfo importPropertyInfo in activationTypeInfo.ImportProperties)
			{
				PropertyInfo propertyInfo = importPropertyInfo.PropertyInfo as PropertyInfo;

				if (propertyInfo != null)
				{
					if (propertyInfo.PropertyType == typeof(IDependencyInjectionContainer))
					{
						BinaryExpression assignProperty =
							Expression.Assign(Expression.Property(returnValue, propertyInfo), containerParameter);

						returnExpressions.Add(assignProperty);
					}
					else
					{
						string exportName = importPropertyInfo.ExportName;
						object[] injectionTargetAttributes = propertyInfo.GetCustomAttributes().ToArray();

						if (propertyInfo.PropertyType != typeof(IValidationContext))
						{
							ParameterExpression variable =
								CreateImportVariableExpression(importStatements,
																		 injectionContextParameter,
																		 strategy,
																		 returnValue,
																		 importPropertyInfo.ExportActivator,
																		 propertyInfo.PropertyType,
																		 exportName,
																		 importPropertyInfo.IsVolatile,
																		 importPropertyInfo.IsRequired,
																		 propertyInfo,
																		 injectionTargetAttributes);

							BinaryExpression assignProperty =
								Expression.Assign(Expression.Property(returnValue, propertyInfo),
														Expression.Convert(variable,
																				 propertyInfo.PropertyType));

							returnExpressions.Add(assignProperty);
						}
						else
						{
							MethodInfo closedLocate = internalLocateInfo.MakeGenericMethod(typeof(IValidationContext));

							BinaryExpression assignProperty =
								Expression.Assign(Expression.Property(returnValue, propertyInfo),
														Expression.Call(strategy,
																			 closedLocate,
																			 injectionContextParameter,
																			 Expression.Constant(string.Empty),
																			 returnValue));

							returnExpressions.Add(assignProperty);
						}
					}
				}
			}

			return returnExpressions;
		}

		private IEnumerable<Expression> ProcessMethods(List<Expression> importExpressions,
											 ParameterExpression injectionContextParameter,
											 ParameterExpression strategy,
											 ParameterExpression returnValue,
											 out MethodInfo completeMethod)
		{
			List<Expression> returnExpressions = new List<Expression>();

			completeMethod = null;

			if (activationTypeInfo.DiscoverAttributes)
			{
				returnExpressions.AddRange(
					DiscoverImportMethods(ActivationType,
											 importExpressions,
											 injectionContextParameter,
											 strategy,
											 returnValue,
											 ref completeMethod));
			}
			else if (activationTypeInfo.ImportMethods != null)
			{
				foreach (ImportMethodInfo importMethodInfo in activationTypeInfo.ImportMethods)
				{
					MethodInfo methodInfo =
						activationTypeInfo.ActivationType.GetTypeInfo().GetDeclaredMethod(importMethodInfo.MethodName);

					if (methodInfo != null)
					{
						returnExpressions.AddRange(
							CreateImportMethodExpression(methodInfo, importExpressions, injectionContextParameter, strategy, returnValue));
					}
				}
			}

			if (completeMethod == null &&
				 !string.IsNullOrEmpty(activationTypeInfo.ActivationMethod))
			{
				try
				{
					foreach (MethodInfo declaredMethod in
						ActivationType.GetTypeInfo().GetDeclaredMethods(activationTypeInfo.ActivationMethod))
					{
						ParameterInfo[] parameterInfos = declaredMethod.GetParameters();

						if (parameterInfos.Length == 0)
						{
							completeMethod = declaredMethod;
							break;
						}
					}
				}

				catch (Exception exp)
				{
					Logger.Error("Exception thrown while getting Activation Method", supplemental, exp);
				}
			}

			return returnExpressions;
		}

		private IEnumerable<Expression> DiscoverImportMethods(Type activationType,
													  List<Expression> importExpressions,
													  ParameterExpression injectionContextParameter,
													  ParameterExpression strategy,
													  ParameterExpression returnValue,
													  ref MethodInfo completeMethod)
		{
			List<Expression> returnExpressions = new List<Expression>();
			Type baseType = activationType.GetTypeInfo().BaseType;

			if (baseType != null && baseType != typeof(object))
			{
				returnExpressions.AddRange(
					DiscoverImportMethods(baseType, importExpressions, injectionContextParameter, strategy, returnValue, ref completeMethod));
			}

			foreach (MethodInfo methodInfo in activationType.GetTypeInfo().DeclaredMethods)
			{
				if (!methodInfo.IsPublic || methodInfo.IsStatic)
				{
					continue;
				}

				foreach (Attribute methodAttribute in methodInfo.GetCustomAttributes(true))
				{
					if (methodAttribute is IImportMethodAttribute)
					{
						returnExpressions.AddRange(
							CreateImportMethodExpression(methodInfo, importExpressions, injectionContextParameter, strategy, returnValue));
					}
					else if (methodInfo.GetParameters().Length == 1)
					{
						bool? background = null;

						if (methodAttribute is IMessageHandlerAttribute)
						{
							background = false;
						}
						else if (methodAttribute is IBackgroundMessageHandlerAttribute)
						{
							background = true;
						}

						if (background.HasValue)
						{
							MethodInfo closedRegister = messengerRegisterInfo.MakeGenericMethod(
								methodInfo.GetParameters()[0].ParameterType);

							returnExpressions.Add(
								Expression.Call(strategy,
													 closedRegister,
													 returnValue,
													 Expression.Constant(methodInfo.Name),
													 Expression.Constant(background.Value)));
						}
					}
					else if (methodAttribute is IActivationCompleteAttribute &&
								methodInfo.GetParameters().Length == 0)
					{
						completeMethod = methodInfo;
					}
				}
			}

			return returnExpressions;
		}

		private IEnumerable<Expression> CreateImportMethodExpression(MethodInfo methodInfo,
																List<Expression> importExpressions,
																ParameterExpression injectionContextParameter,
																ParameterExpression strategy,
																ParameterExpression returnValue)
		{
			List<Expression> expressions = new List<Expression>();
			List<Expression> callParameters = new List<Expression>();

			foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
			{
				bool isVolatile = false;
				bool isRequired = true;
				string exportName = null;
				IExportActivator activator = null;

				if (parameterInfo.ParameterType != typeof(IValidationContext))
				{
					object[] injectionTargetAttributes = parameterInfo.GetCustomAttributes().ToArray();

					foreach (object customAttribute in injectionTargetAttributes)
					{
						IImportAttribute attribute = customAttribute as IImportAttribute;

						if (attribute != null)
						{
							IImportAttributeInfo attributeInfo = attribute.ProvideImportInfo(parameterInfo);

							isRequired = attributeInfo.Required;
							exportName = attributeInfo.ImportName;
							isVolatile = attributeInfo.Volatile;
							activator = attributeInfo.ExportActivator;
							break;
						}

					}

					ParameterExpression varaible =
						CreateImportVariableExpression(importExpressions,
						                               injectionContextParameter,
						                               strategy,
						                               returnValue,
						                               activator,
						                               parameterInfo.ParameterType,
						                               exportName,
						                               isVolatile,
						                               isRequired,
						                               parameterInfo,
						                               injectionTargetAttributes);

					callParameters.Add(varaible);
				}
				else
				{
					MethodInfo closedLocate = internalLocateInfo.MakeGenericMethod(typeof(IValidationContext));

					Expression validationContextImport = Expression.Call(strategy,
					                closedLocate,
					                injectionContextParameter,
					                Expression.Constant(string.Empty),
					                returnValue);

					callParameters.Add(validationContextImport);
				}
			}

			expressions.Add(Expression.Call(returnValue, methodInfo, callParameters));

			return expressions;
		}

		/// <summary>
		/// This method sets the InjectionType property on the injectionContext
		///  creating statement injectionContext.InjectionType = injectionType;
		/// </summary>
		/// <param name="injectionContextParameter"></param>
		/// <param name="injectionType"></param>
		/// <param name="injectionTarget"></param>
		/// <param name="injectionTargetAttributes"></param>
		/// <returns></returns>
		private Expression CreateInjectionTargetSetStatement(ParameterExpression injectionContextParameter,
																  Type injectionType,
																  object injectionTarget,
																  object[] injectionTargetAttributes)
		{
			InjectionTargetInfo info = new InjectionTargetInfo
													{
														InjectionType = injectionType,
														InjectionTarget = injectionTarget,
														InjectionTargetAttributes = injectionTargetAttributes
													};

			// creating statement injectionContext.InjectionType = injectionType;
			return
				Expression.Assign(Expression.Property(injectionContextParameter, "Target"), Expression.Constant(info));
		}

		/// <summary>
		/// All imports are located before the object is created. Call this method to get an import variable
		/// </summary>
		/// <returns></returns>
		private ParameterExpression CreateImportVariableExpression(List<Expression> importStatements,
																	  ParameterExpression injectionContextParameter,
																	  ParameterExpression strategy,
																	  ParameterExpression returnValue,
																	  IExportActivator activator,
																	  Type importType,
																	  string importName,
																	  bool volatileImport,
																	  bool isRequired,
																	  object targetInfo,
																	  object[] targetInjectionAttributes)
		{
			ParameterExpression variableExpression = Expression.Variable(importType);

			importedVariables.Add(variableExpression);

			Expression contextValues = CreateInjectionTargetSetStatement(injectionContextParameter,
																									 activationTypeInfo.ActivationType,
																									 targetInfo,
																									 targetInjectionAttributes);

			if (contextValues != null)
			{
				importStatements.Add(contextValues);
			}

			Expression valueProvideExpression =
				CreateImportValueExpression(injectionContextParameter,
													 strategy,
													 returnValue,
													 activator,
													 importType,
													 importName,
													 volatileImport);

			BinaryExpression assignProperty =
				Expression.Assign(variableExpression,
										Expression.Convert(valueProvideExpression,
																 importType));

			importStatements.Add(assignProperty);

			if (isRequired && !importType.GetTypeInfo().IsValueType)
			{
				Expression throwExpression =
					Expression.Throw(
						Expression.New(exceptionConstructorInfo,
											Expression.Constant(
												string.Format("Could not import type {0} while activating {1}",
																  importType.FullName, ActivationType.FullName))));

				Expression ifNullThrowNotFound =
					Expression.IfThen(Expression.Equal(variableExpression,
																  Expression.Constant(null)),
											throwExpression);

				importStatements.Add(ifNullThrowNotFound);
			}

			return variableExpression;
		}

		private Expression CreateImportValueExpression(ParameterExpression injectionContextParameter,
																	  ParameterExpression strategy,
																	  ParameterExpression returnValue,
																	  IExportActivator activator,
																	  Type importType,
																	  string exportName,
																	  bool volatileImport)
		{
			Expression valueProvideExpression = null;

			if (importType.GetTypeInfo().IsGenericType &&
				 importType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Lazy<>))
			{
				MethodInfo closedLocate =
					lazyLocateInfo.MakeGenericMethod(importType.GenericTypeArguments[0]);

				if (string.IsNullOrEmpty(exportName))
				{
					exportName = string.Empty;
				}

				valueProvideExpression = Expression.Call(strategy,
																	  closedLocate,
																	  injectionContextParameter,
																	  Expression.Constant(exportName),
																	  returnValue);
			}
			else if (importType.FullName.StartsWith("System.Func`"))
			{
				valueProvideExpression = CreateFuncFactoryExpression(importType, exportName, strategy);
			}

			if (valueProvideExpression == null)
			{
				IExportActivator currentActivator = activator;
				string nameToCheck = exportName;

				if (string.IsNullOrEmpty(nameToCheck))
				{
					nameToCheck = importType.FullName;
				}

				IExportStrategy exportStrategy = null;
				object singletonValue = null;

				if (!volatileImport && currentActivator == null)
				{
					exportStrategy = Container.GetStrategy(nameToCheck);

					if (exportStrategy != null && !exportStrategy.Conditions().Any())
					{
						if (exportStrategy.LifeCycleContainer is SingletonLifeCycleContainer &&
							(!exportStrategy.IsCoreExport ||
							exportStrategy.CoreExportStartOrder > (int)CoreExportStartOrder.UI + 1))
						{
							singletonValue = exportStrategy.Activate(Container.CreateInjectionContext(), null);
						}
						else
						{
							currentActivator = exportStrategy;
						}
					}
				}

				if (singletonValue != null)
				{
					valueProvideExpression = Expression.Constant(singletonValue);
				}
				else if (currentActivator != null)
				{
					valueProvideExpression = Expression.Call(Expression.Constant(currentActivator),
																		  exportStrategyActivateMethodInfo,
																		  injectionContextParameter,
																		  Expression.Constant(new object[] { }));
				}
				else
				{
					MethodInfo closedLocate =
						internalLocateInfo.MakeGenericMethod(importType);

					if (exportName == null)
					{
						exportName = string.Empty;
					}

					valueProvideExpression = Expression.Call(strategy,
																		  closedLocate,
																		  injectionContextParameter,
																		  Expression.Constant(exportName),
																		  returnValue);
				}
			}

			return valueProvideExpression;
		}

		private Expression CreateFuncFactoryExpression(Type importType, string exportName, ParameterExpression strategy)
		{
			if (string.IsNullOrEmpty(exportName))
			{
				exportName = string.Empty;
			}

			Type[] typeArguements = importType.GetTypeInfo().GenericTypeArguments;

			MethodInfo factoryMethod = null;

			if (typeArguements.Length == 1)
			{
				factoryMethod = createFuncFactoryMethodInfo.MakeGenericMethod(typeArguements[0]);
			}
			else if (typeArguements.Length == 2 && typeArguements[0] == typeof(IInjectionContext))
			{
				factoryMethod = createFuncFactoryWithContextMethodInfo.MakeGenericMethod(typeArguements[1]);
			}
			else if (typeArguements.Length == 3 &&
						typeArguements[0] == typeof(IInjectionContext) && typeArguements[1] == typeof(object[]))
			{
				factoryMethod = createFuncFactoryWithContextAndParamsMethodInfo.MakeGenericMethod(typeArguements[2]);
			}

			if (factoryMethod != null)
			{
				return Expression.Call(strategy, factoryMethod, Expression.Constant((string)exportName));
			}

			return null;
		}

		private Expression GenerateConstructors(List<Expression> importStatements,
															 ParameterExpression strategy,
															 ParameterExpression injectionContextParameter,
															 ParameterExpression constructorParams,
															 ParameterExpression returnValue)
		{
			ConstructorInfo importConstructor = activationTypeInfo.ImportConstructorInfo as ConstructorInfo;
			bool onlyImport = importConstructor != null;
			List<ConstructorInfo> infoList = new List<ConstructorInfo>(
				ActivationType.GetTypeInfo().DeclaredConstructors);

			infoList.Sort((x, y) =>
							  Comparer<int>.Default.Compare(x.GetParameters().Length, y.GetParameters().Length));

			infoList.Reverse();

			if (importConstructor == null)
			{
				if (infoList.Count > 1)
				{
					foreach (ConstructorInfo constructorInfo in infoList)
					{
						foreach (Attribute customAttribute in constructorInfo.GetCustomAttributes(true))
						{
							if (customAttribute is IImportConstructorAttribute)
							{
								onlyImport = true;
								importConstructor = constructorInfo;
								break;
							}
						}

						if (importConstructor != null)
						{
							break;
						}
					}
				}
				else
				{
					importConstructor = infoList[0];
				}
			}

			if (importConstructor == null)
			{
				return
					GenerateNonImportConstructors(infoList, constructorParams, returnValue);
			}

			return GenerateImportConstructor(onlyImport, importStatements,
				importConstructor, strategy, injectionContextParameter, constructorParams, returnValue);
		}

		private Expression GenerateImportConstructor(bool onlyImport,
																	List<Expression> importStatements,
																	ConstructorInfo importConstructor,
																	ParameterExpression strategy,
																	ParameterExpression injectionContextParameter,
																	ParameterExpression constructorParams,
																	ParameterExpression returnValue)
		{
			ParameterInfo[] parameterInfos = importConstructor.GetParameters();
			List<ParameterExpression> variableExpressions = new List<ParameterExpression>();

			if (parameterInfos.Length > 0)
			{
				List<Expression> constructorImportStatements = new List<Expression>();
				List<Expression> assignFromParamsStatements = new List<Expression>();

				for (int index = 0; index < parameterInfos.Length; index++)
				{
					ParameterInfo parameterInfo = parameterInfos[index];
					bool isVolatile = false;
					bool isRequired = true;
					string importName = null;

					object[] targetInjectionAttributes = null;

					IEnumerable<Attribute> attributes =
					parameterInfo.GetCustomAttributes();

					if (attributes != null)
					{
						targetInjectionAttributes = attributes.ToArray();
					}
					else
					{
						targetInjectionAttributes = new object[0];
					}

					foreach (object customAttribute in targetInjectionAttributes)
					{
						IImportAttribute attribute = customAttribute as IImportAttribute;

						if (attribute != null)
						{
							IImportAttributeInfo attributeInfo = attribute.ProvideImportInfo(parameterInfo);

							isRequired = attributeInfo.Required;
							importName = attributeInfo.ImportName;
							isVolatile = attributeInfo.Volatile;
							break;
						}
					}

					if (importName == null)
					{
						importName = string.Empty;
					}

					ParameterExpression parameterExpression = CreateImportVariableExpression(constructorImportStatements,
																													 injectionContextParameter,
																													 strategy,
																													 returnValue,
																													 null,
																													 parameterInfo.ParameterType,
																													 importName,
																													 isVolatile,
																													 isRequired,
																													 parameterInfo,
																													 targetInjectionAttributes);

					variableExpressions.Add(parameterExpression);

					if (!onlyImport)
					{
						assignFromParamsStatements.Add(Expression.Convert(Expression.Property(constructorParams,
																																			 objectArrayIndexInfo,
																																			 Expression.Constant(index)),
																												  parameterInfo.ParameterType));
					}
				}

				if (onlyImport)
				{
					importStatements.AddRange(constructorImportStatements);

					return Expression.Assign(returnValue, Expression.New(importConstructor, variableExpressions));
				}
				else
				{

					Expression constructorBlockStatments = Expression.Assign(returnValue,
																							 Expression.New(importConstructor, assignFromParamsStatements));
					Expression importAssignReturnValue = Expression.Assign(returnValue,
																							 Expression.New(importConstructor, variableExpressions));

					constructorImportStatements.Add(importAssignReturnValue);

					BlockExpression importConstructorBlock = Expression.Block(constructorImportStatements);


					return Expression.IfThenElse(Expression.OrElse(Expression.Equal(constructorParams,
																								Expression.Constant(null)),
																		 Expression.Equal(
																			 Expression.Property(constructorParams, objectArrayLengthInfo),
																			 Expression.Constant(0))),
													 importConstructorBlock,
													 constructorBlockStatments);
				}
			}

			return Expression.Assign(returnValue, Expression.New(importConstructor));
		}

		private Expression GenerateNonImportConstructors(List<ConstructorInfo> infoList,
																		 ParameterExpression constructorParams,
																		 ParameterExpression returnValue)
		{
			int startIndex = 0;
			ConstructorInfo parameterLessConst = null;

			Expression returnExpression = Expression.Throw(
				Expression.New(typeof(Exception)));

			foreach (ConstructorInfo constructorInfo in infoList)
			{
				bool skipConstructor = false;
				ParameterInfo[] parameters = constructorInfo.GetParameters();

				if (parameters.Length > 0)
				{
					Expression andStatement = null;

					List<Expression> arguements = new List<Expression>();

					int i = 0;
					foreach (ParameterInfo parameter in parameters)
					{
						string typeName = parameter.ParameterType.FullName;

						// Skip these namespaces because they contain types
						// that can't be used in Linq expressions
						if (typeName.StartsWith("System.Runtime") ||
							 typeName.StartsWith("System.Reflection"))
						{
							skipConstructor = true;
							break;
						}

						Expression testStatement = null;
						int tempI = i;
						arguements.Add(
							Expression.Convert(Expression.Property(constructorParams,
																				objectArrayIndexInfo,
																				Expression.Constant(tempI)),
													 parameter.ParameterType));

						if (parameter.ParameterType.GetTypeInfo().IsValueType)
						{
							Expression property =
								Expression.Property(constructorParams,
														  objectArrayIndexInfo,
														  Expression.Constant(tempI));

							testStatement = Expression.TypeIs(property, parameter.ParameterType);
						}
						else
						{
							Expression property =
								Expression.Property(constructorParams,
														  objectArrayIndexInfo,
														  Expression.Constant(tempI));

							testStatement = Expression.TypeIs(property, parameter.ParameterType);

							Expression testNull =
								Expression.Equal(property, Expression.Constant(null));

							testStatement = Expression.Or(testStatement, testNull);
						}

						if (andStatement != null)
						{
							andStatement = Expression.AndAlso(testStatement, andStatement);
						}
						else
						{
							andStatement = testStatement;
						}

						i++;
					}

					if (!skipConstructor)
					{
						Expression lengthCheck =
							Expression.Equal(Expression.Property(constructorParams, objectArrayLengthInfo),
												  Expression.Constant(parameters.Length));

						Expression finalTestStatement
							= Expression.AndAlso(lengthCheck, andStatement);

						Expression assignment = Expression.Assign(returnValue,
																				Expression.New(constructorInfo, arguements));

						Expression ifThenExpression =
							Expression.IfThenElse(finalTestStatement, assignment, returnExpression);

						returnExpression = ifThenExpression;
					}
				}
				else if (!constructorInfo.IsStatic && constructorInfo.IsPublic)
				{
					parameterLessConst = constructorInfo;
				}
			}

			if (parameterLessConst != null)
			{
				Expression assignStatement =
					Expression.Assign(returnValue, Expression.New(ActivationType));

				returnExpression =
					Expression.IfThenElse(
						Expression.OrElse(
							Expression.Equal(constructorParams, Expression.Constant(null)),
							Expression.Equal(Expression.Property(constructorParams, objectArrayLengthInfo), Expression.Constant(0))),
						assignStatement,
						returnExpression);
			}
			else
			{
				Expression throwStatement =
					Expression.Throw(Expression.New(typeof(Exception)));

				returnExpression =
					Expression.IfThenElse(Expression.Or(
						Expression.Equal(constructorParams, Expression.Constant(null)),
						Expression.Equal(Expression.Property(constructorParams, objectArrayLengthInfo), Expression.Constant(0))),
												 throwStatement,
												 returnExpression);
			}

			return returnExpression;
		}

		internal T Locate<T>(IInjectionContext injectionContext, string exportName, object targetObject)
		{
			if (typeof(T) == typeof(IValidationContext))
			{
				if (targetObject == null)
				{
					throw new Exception("You can only import an IValidationContext into a property, not a constructor");
				}

				IValidationService validationService = (IValidationService)Container.LocateByType(typeof(IValidationService));

				if (validationService == null)
				{
					throw new Exception("Could not locate IValidationService");
				}

				return (T)validationService.GetValidationContext(targetObject);
			}

			if (string.IsNullOrEmpty(exportName))
			{
				return (T)Container.LocateByTypeWithContext(injectionContext, typeof(T));
			}

			return (T)Container.LocateWithContext(injectionContext, exportName);
		}

		internal Lazy<T> LazyLocate<T>(IInjectionContext injectionContext, string exportName, object targetObject)
		{
			return new Lazy<T>(() =>
			{
				if (typeof(T) == typeof(IValidationContext))
				{
					IValidationService validationService =
						(IValidationService)Container.LocateByType(typeof(IValidationService));

					return (T)validationService.GetValidationContext(targetObject);
				}

				if (string.IsNullOrEmpty(exportName))
				{
					return (T)Container.LocateByTypeWithContext(injectionContext, typeof(T));
				}
				else
				{
					return (T)Container.LocateWithContext(injectionContext, exportName);
				}
			});
		}

		internal void RegisterMessengerHandler<T>(object target, string methodName, bool background)
		{
			if (messenger == null)
			{
				messenger =
					Container.LocateByType(typeof(IDispatchedMessenger)) as IDispatchedMessenger;
			}

			if (messenger != null)
			{
				messenger.RegisterMethod(target, methodName, background, false);
			}
		}

		private object EnrichWith(object injectedObject, IInjectionContext injectionContext, IEnumerable<EnrichWithDelegate> enrichWithDelegates)
		{
			object returnValue = injectedObject;

			foreach (EnrichWithDelegate enrichWithDelegate in enrichWithDelegates)
			{
				returnValue = enrichWithDelegate(Container, injectionContext, returnValue);
			}

			return returnValue;
		}

		private Func<T> CreateFuncFactory<T>(string exportName)
		{
			if (string.IsNullOrEmpty(exportName))
			{
				return () => (T)Container.LocateByType(typeof(T));
			}

			return () => (T)Container.Locate(exportName);
		}

		private Func<IInjectionContext, T> CreateFuncFactoryWithContext<T>(string exportName)
		{
			if (string.IsNullOrEmpty(exportName))
			{
				return x => (T)Container.LocateByTypeWithContext(x, typeof(T));
			}

			return x => (T)Container.LocateWithContext(x, exportName);
		}

		private Func<IInjectionContext, object[], T> CreateFuncFactoryWithContextAndParams<T>(string exportName)
		{
			if (string.IsNullOrEmpty(exportName))
			{
				return (x, y) => (T)Container.LocateByTypeWithContext(x, typeof(T), y);
			}

			return (x, y) => (T)Container.LocateWithContext(x, exportName, y);
		}



		private T CreateUsingExportFunction<T>(IDependencyInjectionContainer container,
													IInjectionContext injectionContext,
													object[] constructorParams)
		{
			return (T)exportFunction(container, injectionContext, constructorParams);
		}

		private BlockExpression customActivationLogic(ParameterExpression containerParameter,
																	 ParameterExpression constructorParam,
																	 ParameterExpression returnValue)
		{
			return null;
		}

	}
}